Azure AD by default uses a certificate to sign an OAuth2 JWT token using an asymmetric algorithm (RS256). Alternatively a JWT token can be signed with a “shared” secret using a symmetric algorithm (HS256). Asymmetric signing algorithm is always more secure in preventing the token to be tampered with compared to a symmetric algorithm since the private key is always kept at the Identity Provider (IDP) and the token consumer only has access to the public key to verify the token signature. In certain situations, for instance the token consumer can’t handle asymmetric key signing, it may be desirable to have an Azure AD generated JWT token signed with a symmetric key. This blog will show you:
- How to configure Azure AD to issue a symmetric key signing id_token for a particular application and
- How to use an OpenID Connect ASP.Net application to validate this symmetrically signed id_token
Configuring an Azure AD App Registration with signing secret
- Create an App Registration in Azure AD. For reply URL, use the appropriate reply URL from your application.
- Take note of both the Object ID and the Application (client) ID from the Application Overview blade
- Configure this application with a signing secret
For this part, I am using the following values to demonstrate the concept. An application signing key in Azure AD is encapsulated in the keyCredentials attribute. Each keyCredential object has a CustomKeyIdentifier and a key attribute. See https://docs.microsoft.com/en-us/azure/active-directory/develop/reference-app-manifest#keycredentials-attribute for more info.
CustomKeyIdentifier: “My Cool Key“
Secret used for signing (aka key): “SuperSecretPassword2“
There are 2 ways to configure a secret key for signing:
- Use Azure AD PowerShell commandlet New-AzureADApplicationKeyCredential as below:
# Log in to Azure AD tenant Connect-AzureAD # Create a symmetric signing key with a value of SuperSecretPassword2 New-AzureADApplicationKeyCredential -ObjectId <Application Object ID> -CustomKeyIdentifier "My Cool Key" -Type Symmetric -Usage Sign -Value "SuperSecretPassword2" -StartDate "10/24/2020" -EndDate "9/13/2021"
- or use Microsoft Graph to update this Application with the signing key secret.
For this part, we will need to Base64-Encode both the secret and the CustomKeyIdentifier. The MS Graph request and request body should be similar to the following:
PATCH https://graph.microsoft.com/v1.0/applications/<Application Object ID> Request Body: { "keyCredentials": [{ "customKeyIdentifier": "TXkgQ29vbCBLZXk=", // base64 encoded string "My Cool Key" "displayName": "My symmetric key", "endDateTime": "2021-09-13T00:00:00Z", "keyId": "31d6106a-f8d8-415b-a15b-a1dcd64e6841", "startDateTime": "2020-10-24T07:25:00Z", "type": "Symmetric", "usage": "Sign", "key": "U3VwZXJTZWNyZXRQYXNzd29yZDI=" // base64 encoded string "SuperSecretPassword2" }] }
You can perform this step using MS Graph Explorer tool:
Note: OpenID Connect OWIN middleware expects a signing symmetric key to have a minimum length of 16 characters. You might see the errors similar to the following if the signing secret is too short:
IDX10503: Signature validation failed. Keys tried: 'Microsoft.IdentityModel.Tokens.SymmetricSecurityKey , KeyId: Microsoft.IdentityModel.Tokens.X509SecurityKey , KeyId: kg2LYs2T0CTjIfj4rt6JIynen38 Microsoft.IdentityModel.Tokens.RsaSecurityKey , KeyId: kg2LYs2T0CTjIfj4rt6JIynen38 Microsoft.IdentityModel.Tokens.X509SecurityKey , KeyId: 18pnMg3UmrWvBK_tkDAbjgM5CmA Microsoft.IdentityModel.Tokens.RsaSecurityKey , KeyId: 18pnMg3UmrWvBK_tkDAbjgM5CmA '. Exceptions caught: 'System.ArgumentOutOfRangeException: IDX10603: Decryption failed. Keys tried: 'HS256'. Exceptions caught: '128'. token: '64' Parameter name: KeySize at Microsoft.IdentityModel.Tokens.SymmetricSignatureProvider..ctor(SecurityKey key, String algorithm, Boolean willCreateSignatures) in C:\agent2_work\56\s\src\Microsoft.IdentityModel.Tokens\SymmetricSignatureProvider.cs:line 47 at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateSignatureProvider(SecurityKey key, String algorithm, Boolean willCreateSignatures) in C:\agent2_work\56\s\src\Microsoft.IdentityModel.Tokens\CryptoProviderFactory.cs:line 446 at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateForVerifying(SecurityKey key, String algorithm) in C:\agent2_work\56\s\src\Microsoft.IdentityModel.Tokens\CryptoProviderFactory.cs:line 249
Once a symmetric signing key is configured, your id_token received from Azure AD V1 endpoint for this app should look similar to the following in https://jwt.ms:
Configuring OpenID Connect OWIN middleware to use this symmetric key:
For this part I followed the tutorial at https://docs.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-asp-webapp to create an ASP.Net MVC Web Application and made some minor changes to the Token Validation Parameters to configure to use the above symmetric key. This project uses the Application ID, reply URL, and tenant ID of the application we create at the beginning of this post. Below is my complete Startup.cs file. The key to this is to set the IssuerSigningKey field in the TokenValidationParameters object.
using System; using System.Text; using System.Threading.Tasks; using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Microsoft.IdentityModel.Tokens; using Microsoft.Owin; using Microsoft.Owin.Security; using Microsoft.Owin.Security.Cookies; using Microsoft.Owin.Security.Notifications; using Microsoft.Owin.Security.OpenIdConnect; using Owin; [assembly: OwinStartup(typeof(NetWebAppOIDC.Startup))] namespace NetWebAppOIDC { public class Startup { // Appllication ID of the registered app string clientId = System.Configuration.ConfigurationManager.AppSettings["ClientId"]; // you can use the home page of this app for redirect URI. Make sure it's the same in App Registration reply URL string redirectUri = System.Configuration.ConfigurationManager.AppSettings["RedirectUri"]; // Directory ID static string tenant = System.Configuration.ConfigurationManager.AppSettings["Tenant"]; string authority = String.Format(System.Globalization.CultureInfo.InvariantCulture, System.Configuration.ConfigurationManager.AppSettings["Authority"], tenant); public void Configuration(IAppBuilder app) { // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=316888 app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType); app.UseCookieAuthentication(new CookieAuthenticationOptions()); app.UseOpenIdConnectAuthentication( new OpenIdConnectAuthenticationOptions { // Sets the ClientId, authority, RedirectUri as obtained from web.config ClientId = clientId, Authority = authority, RedirectUri = redirectUri, // PostLogoutRedirectUri is the page that users will be redirected to after sign-out. In this case, it is using the home page PostLogoutRedirectUri = redirectUri, Scope = OpenIdConnectScope.OpenIdProfile, // ResponseType is set to request the id_token - which contains basic information about the signed-in user ResponseType = OpenIdConnectResponseType.IdToken, // ValidateIssuer set to false to allow personal and work accounts from any organization to sign in to your application // To only allow users from a single organizations, set ValidateIssuer to true and 'tenant' setting in web.config to the tenant name // To allow users from only a list of specific organizations, set ValidateIssuer to true and use ValidIssuers parameter TokenValidationParameters = new TokenValidationParameters() { IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("SuperSecretPassword2")) }, // OpenIdConnectAuthenticationNotifications configures OWIN to send notification of failed authentications to OnAuthenticationFailed method Notifications = new OpenIdConnectAuthenticationNotifications { AuthenticationFailed = OnAuthenticationFailed } } ); Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true; } private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context) { context.HandleResponse(); context.Response.Redirect("/?errormessage=" + context.Exception.Message); return Task.FromResult(0); } } }
Without a valid symmetric key, you might run into the following Signature Validation error:
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code. Exception Details: Microsoft.IdentityModel.Tokens.SecurityTokenInvalidSignatureException: IDX10503: Signature validation failed. Keys tried: 'Microsoft.IdentityModel.Tokens.X509SecurityKey , KeyId: kg2LYs2T0CTjIfj4rt6JIynen38 Microsoft.IdentityModel.Tokens.RsaSecurityKey , KeyId: kg2LYs2T0CTjIfj4rt6JIynen38 Microsoft.IdentityModel.Tokens.X509SecurityKey , KeyId: M6pX7RHoraLsprfJeRCjSxuURhc Microsoft.IdentityModel.Tokens.RsaSecurityKey , KeyId: M6pX7RHoraLsprfJeRCjSxuURhc Microsoft.IdentityModel.Tokens.X509SecurityKey , KeyId: 18pnMg3UmrWvBK_tkDAbjgM5CmA Microsoft.IdentityModel.Tokens.RsaSecurityKey , KeyId: 18pnMg3UmrWvBK_tkDAbjgM5CmA '. Exceptions caught: 'System.NotSupportedException: IDX10634: Unable to create the SignatureProvider. Algorithm: 'HS256', SecurityKey: 'Microsoft.IdentityModel.Tokens.X509SecurityKey' is not supported. at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateSignatureProvider(SecurityKey key, String algorithm, Boolean willCreateSignatures) in C:\agent2_work\56\s\src\Microsoft.IdentityModel.Tokens\CryptoProviderFactory.cs:line 373 at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateForVerifying(SecurityKey key, String algorithm) in C:\agent2_work\56\s\src\Microsoft.IdentityModel.Tokens\CryptoProviderFactory.cs:line 249
References
Secure Your ASP.NET Web Forms Application with OpenID Connect and Okta
How to Validate Apigee Edge generated JWT Token from .NET/C# code?
Error: IDX10603: Decryption failed. Keys tried: [PII is hidden].Parameter name: KeySize
RS256 vs HS256: What’s the difference?
JWT: Symmetic and Asymmetic Key Authentication In ASP.NET Core